﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

using LINQToolKit.Query;
using LINQToolKit.Query.Rules;

namespace LINQToolKit.GData
{
    /// <summary>
    /// Represents the expression translator for the google data api
    /// </summary>
    /// <remarks>
    /// This framework is under the GNU licence and Creative Commons 3.0.
    /// You can also get a copy of the source code from google code
    /// <see cref="http://linqtodouban.googlecode.com/svn/trunk/"/>.
    /// For more information and further support, please contact with kevin (chwkai@gmail.com)
    /// </remarks>
    public class GDataTranslator : RuleTranslator
    {
        IDictionary<string, string> _queryStrings;
        private string _keyword;

        public GDataTranslator(QueryRule rule)
            : base(rule)
        {
            this._queryStrings = new Dictionary<string, string>();
            this._keyword = string.Empty;
        }

        public override IList<string> GetQueries(Expression expression)
        {
            this._keyword = string.Empty;
            this._queryStrings.Clear();
            var queries = base.GetQueries(expression);

            // apending the gdata query strings;
            if (this._queryStrings.Count > 0 || !string.IsNullOrEmpty(this._keyword))
            {
                for (int i = 0; i < queries.Count; i++)
                {
                    queries[i] += (string.IsNullOrEmpty(this._keyword) ? 
                        string.Empty : "q=" + this._keyword);

                    foreach (var item in this._queryStrings.Keys)
                    {
                        queries[i] +=string.Format("&{0}={1}", item, this._queryStrings[item]);
                            
                    }
                }
            }

            return queries;
        }

        protected override bool IsQueryMethod(MethodInfo methodInfo)
        {
            return methodInfo.DeclaringType == typeof(GDataQueryExt) || base.IsQueryMethod(methodInfo);
        }

        protected void IncludeKeyword(string value)
        {
            this._keyword += string.Format("\"{0}\" ", value);
        }

        protected void ExcludeKeyword(string value)
        {
            this._keyword += string.Format("-\"{0}\" ", value);
        }

        protected void AppendQueryString(string name, string value)
        {
            if (this._queryStrings.ContainsKey(name))
            {
                this._queryStrings[name] = value;
            }
            else
            {
                this._queryStrings.Add(name, value);
            }
        }

        protected override string TranslateQueryString(MethodCallExpression m)
        {
            switch (m.Method.Name)
            {
                case "Skip":
                    {
                        var value = ((ConstantExpression)m.Arguments[1]).Value.ToString();
                        this.AppendQueryString("start-index", value);
                        break;
                    }
                case "Take":
                    {
                        var value = ((ConstantExpression)m.Arguments[1]).Value.ToString();
                        this.AppendQueryString("max-results", value);
                        break;
                    }
                case "First":
                case "Single":
                case "SingleOrDefault":
                case "FirstOrDefault":
                    {
                        this.AppendQueryString("max-results", "1");
                        break;
                    }
                default:
                    break;
            }

            return base.TranslateQueryString(m);
        }

        protected override string TranslateUnkownQueryMethod(MethodCallExpression m)
        {
            switch (m.Method.Name)
            {
                case "UpdatedBetween":
                    {
                        var start = (DateTime)((ConstantExpression)m.Arguments[1]).Value;
                        var end = (DateTime)((ConstantExpression)m.Arguments[2]).Value;
                        this.AppendQueryString("updated-min", start.ToString("yyyy-MM-ddThh:mm:ssZ"));
                        this.AppendQueryString("updated-max", end.ToString("yyyy-MM-ddThh:mm:ssZ"));
                        break;
                    }
                case "UpdatedMin":
                    {
                        var start = (DateTime)((ConstantExpression)m.Arguments[1]).Value;
                        this.AppendQueryString("updated-min", start.ToString("yyyy-MM-ddThh:mm:ssZ"));
                        break;
                    }
                case "UpdatedMax":
                    {
                        var end = (DateTime)((ConstantExpression)m.Arguments[1]).Value;
                        this.AppendQueryString("updated-max", end.ToString("yyyy-MM-ddThh:mm:ssZ"));
                        break;
                    }
                case "PublishedBetween":
                    {
                        var start = (DateTime)((ConstantExpression)m.Arguments[1]).Value;
                        var end = (DateTime)((ConstantExpression)m.Arguments[2]).Value;
                        this.AppendQueryString("published-min", start.ToString("yyyy-MM-ddThh:mm:ssZ"));
                        this.AppendQueryString("published-max", end.ToString("yyyy-MM-ddThh:mm:ssZ"));
                        break;
                    }
                case "PublishedMin":
                    {
                        var start = (DateTime)((ConstantExpression)m.Arguments[1]).Value;
                        this.AppendQueryString("published-min", start.ToString("yyyy-MM-ddThh:mm:ssZ"));
                        break;
                    }
                case "PublishedMax":
                    {
                        var end = (DateTime)((ConstantExpression)m.Arguments[1]).Value;
                        this.AppendQueryString("published-max", end.ToString("yyyy-MM-ddThh:mm:ssZ"));
                        break;
                    }
                case "Has":
                    {
                        var value = ((ConstantExpression)m.Arguments[1]).Value.ToString();
                        this.IncludeKeyword(value);
                        break;
                    }
                case "NotHas":
                    {
                        var value = ((ConstantExpression)m.Arguments[1]).Value.ToString();
                        this.ExcludeKeyword(value);
                        break;
                    }
                case "InCategory":
                    {
                        var value = ((ConstantExpression)m.Arguments[1]).Value.ToString();
                        this.AppendQueryString("category", value);
                        break;
                    }
                case "AuthorBy":
                    {
                        var value = ((ConstantExpression)m.Arguments[1]).Value.ToString();
                        this.AppendQueryString("author", value);
                        break;
                    }
                case "AltAs":
                    {
                        var value = ((ConstantExpression)m.Arguments[1]).Value.ToString();
                        this.AppendQueryString("alt", value);
                        break;
                    }
                case "PrettyPrint":
                    {
                        var value = ((ConstantExpression)m.Arguments[1]).Value.ToString();
                        this.AppendQueryString("prettyprint", "true");
                        break;
                    }
                case "Strict":
                    {
                        var value = ((ConstantExpression)m.Arguments[1]).Value.ToString();
                        this.AppendQueryString("strict", "true");
                        break;
                    }
                default:
                    break;
            }

            return null;
        }

        protected override TranslatorBase CreateSubTranslator()
        {
            return new GDataTranslator(this.Rule);
        }

        protected override IEqualityComparer<string> GetQueryComparer()
        {
            return new UriEqualityComparer();
        }
    }
}